package agent.strategy;

import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;

import agent.Agent;
import oracle.MoveTypes;
import oracle.Oracle;


/**
 * This is our round one strategy which seeks to explore as much of the world as possible
 * 
 * @author mdailey
 *
 */
public class roundOneStrategy extends Strategy {
	double exploreThresh_1 = .28;	// very low to encourage exploring
	double exploreThresh_2 = .45;	// if we expose 20% of optimal, do it
	
	
	/**
	 * Call our super constructor !!
	 * @param myBoard
	 */
	public roundOneStrategy(BoardMemory myBoard) {
		super(myBoard);
	}

	/** THe max amount of time given to this strategy */
	int MAX_COMMIT_TIME= 13;
	/** The remaining number of moves that we are committed to this strategy */
	int COMMIT_TIME = 13;
	
	/**
	 * This will return our next move which will seek to maximize our utility
	 * @param exploreThresh_1	
	 * @param exploreThresh_2
	 * @param param3
	 */
	public nextDirObject nextMove(double exploreThresh_1, double exploreThresh_2, double param3){
		this.exploreThresh_1 = exploreThresh_1;	// very low to encourage exploring
		this.exploreThresh_2 = exploreThresh_2;	// if we expose 20% of optimal, do it
		return getMove();
	}
	
	
	public boolean worthExploring(){
		
		return false;
	}
	
	public nextDirObject getMoveNew(){
		this.setUpFields();
		int movesZeroWalkAwayDistance = 3;// MAX_COMMIT_TIME;	// walk up to 15 moves away
		int movesZero = COMMIT_TIME;
		/* calculates the maximum possible that could ever be exposed in this map */
		int maxNum = this.getMaxPossTilesEver();
		/* the move that we will return to the user */
		nextDirObject myMove = new nextDirObject();
		myMove.adviseMove = false;
		/* The max we could ever expose from here */
		double a_maxPossHere = this.numPossibleExpose(this.currRow, this.currColumn);
		/* the amount we will expose from here */
		double a_numWillExpose = numWillExpose(this.currRow, this.currColumn);
		/* the percent explored */
		double percExp = this.totalNumExposed()/((double)(maxRow*maxCol));
		/* the maximum amount of tiles that we could currently expose on this current map */
		double bestCurrPossible = 0;
		/* how many tiles we will expose from each location */
		int numExposeAt[][] = new int[maxRow][maxCol];
		/* our max probability of reaching a tile */
		double[][] probReaching = new double[maxRow][maxCol];
		/* an estimate of the worth of a tile */
		double[][] utilToExploreAt = new double[maxRow][maxCol];
		
		/* calculate a value to send into our function to maximize our utility */
		for(int r=0; r<maxRow; r++){
			for(int c=0; c<maxCol; c++){
				numExposeAt[r][c] = numWillExpose(r, c);
				if(numExposeAt[r][c] > bestCurrPossible)
					bestCurrPossible = numExposeAt[r][c];
			}
		}
		
		/* if its worth exploring, just explore */
		if(this.worthItToExplore(0, numWillExpose(this.currRow, this.currColumn), 
				movesZeroWalkAwayDistance, Math.max(this.numMovesRemaining-movesZeroWalkAwayDistance, 0),
				maxNum, this.numRunsRemaining, percExp, bestCurrPossible)){
			myMove.moveDirection = MoveTypes.LOOK_AROUND;
			myMove.adviseMove = true;
			return myMove;
		}
				
		/* calculate a value to send into our function to maximize our utility */
		for(int r=0; r<maxRow; r++){
			for(int c=0; c<maxCol; c++){
				probReaching[r][c]= probReachSquareFromTo(this.currRow,this.currColumn, r, c, movesZeroWalkAwayDistance).probSuccess;
				numExposeAt[r][c] = numWillExpose(r, c);
				if(numExposeAt[r][c] > bestCurrPossible)
					bestCurrPossible = numExposeAt[r][c];
				utilToExploreAt[r][c] = this.getEstimateOnUtility2(probReaching[r][c], numExposeAt[r][c], 
						movesZeroWalkAwayDistance, Math.max(this.numMovesRemaining-movesZeroWalkAwayDistance, 0),
						maxNum, this.numRunsRemaining, percExp, bestCurrPossible);
			}
		}
		
		/* perhaps do some heuristics in here */
		if(movesZero == 1){
			if(numWillExpose(this.currRow, this.currColumn) > exploreThresh_1 * this.numPossibleExpose(this.currRow, this.currColumn) ){
				myMove.moveDirection = MoveTypes.LOOK_AROUND;
				myMove.adviseMove = true;
				return myMove;
			}
		}
	
		myMove = this.getNextDirection(utilToExploreAt, pastTerminals, walls, null, explored, 
				this.currRow, this.currColumn, this.COMMIT_TIME-1, 0,
				this.maxRow, this.maxCol, this.numRunsRemaining, 0);
		return myMove;
	}
	
	
	/**
	 * This is a function that we get to optimize, crazy function, it might work
	 * does not take in the round number, again only designed for round 1
	 * @param probReaching	The probability of reaching this spot in the allotted amount of moves
	 * @param numExposeAt How many tiles we will reveal at that position
	 * @param movesAllowed	Moves allowed to reach this tile
	 * @param movesRemainingAfter	How many moves in this same run we will have left
	 * @param maxPossibleToExplore	The most ever we could ever expose, optimal placement on board
	 * @param RunsRemaining	How many runs we have left through this same map
	 * @param percentExplored Percent of the total map that we have explored
	 * @param the max that we could possible explore currently on this map
	 * @return
	 */
	public boolean worthItToExplore(double probReaching, double numExposeAt, int movesAllowed, 
			double movesRemainingAfter, int maxPossibleToExplore, int RunsRemaining, double percentExplored, 
			double bestCurrPossible ){
		/* Ideally we would like everything to be scaled between 0 and 1 */
		double expExpose = probReaching * numExposeAt;
		double bestPoss = ((double)maxPossibleToExplore)/((double)maxPossibleToExplore+2.0);
		double numTerminalsKnown = getNumTerminals();
		
		// fact_1 ???? is if we have enough moves remaining after
		// fact 2 is on the denominator, represents terminal states
		// fact_3 in the numerator, represents num of runs remaining
		// fact_4 in the numerator, encourages exploring on early rounds
		double fact_1, fact_2, fact_3, fact_4, fact_5, fact_6;
		
		// we have in early maps we explore to find optimal square to get most points
		if(this.myAgent.oracle.getRoundNumber()<= 5){
			fact_4 = 1;
		}else{
			fact_4 = .9;
		}
		if(movesRemainingAfter > 15)
			fact_1 = .9;
		else
			fact_1 = 1;
		if(numTerminalsKnown < this.manhattan / 5){
			// want to be more apt to explore, but this is just saying where we want to explore from
			fact_2 = Math.pow(.95,(int)(this.manhattan/5.0) - numTerminalsKnown);
		}else{
			// want to be less apt to explore, but this is just saying where we want to explore from
			fact_2 = 1;
		}
		// it seems that early runs, mid runs and late runs are fundamentally different in their need to explore
		if(RunsRemaining >= 7){
			fact_3 = .6; 
		}else if(RunsRemaining >= 4){
			fact_3 = .8;
		}else{
			fact_3 = 1;
		}

		double percOfOptimal = numExposeAt/bestCurrPossible;
		if(percOfOptimal > .5 && bestCurrPossible > 1.5*this.manhattan)
			return true;	// may want to add in row constraint
		
		System.out.println((percOfOptimal*fact_3*fact_4/fact_2) + "   " +bestCurrPossible +" >? " +(2*this.manhattan) );
		return percOfOptimal*fact_3*fact_4/fact_2 > .13 && bestCurrPossible > 2*this.manhattan;
		//* fact_3*fact_4/fact_2;
		//return 1 < expExpose/ bestPoss * fact_1 * ((double)RunsRemaining) /((double)RunsRemaining + 5.0)*(Math.exp(percentExplored)/(1+Math.exp(percentExplored)));
		/*(1.0*(maxPossibleToExplore/(maxPossibleToExplore+2))) *
		(movesRemainingAfter/(movesRemainingAfter+1))*
		(RunsRemaining/(RunsRemaining+5))*(Math.exp(percentExplored)/(1+Math.exp(percentExplored)));*/
	}
	
	
	/**
	 * This is a function that we get to optimize, crazy function, it might work
	 * does not take in the round number, again only designed for round 1
	 * @param probReaching	The probability of reaching this spot in the allotted amount of moves
	 * @param numExposeAt How many tiles we will reveal at that position
	 * @param movesAllowed	Moves allowed to reach this tile
	 * @param movesRemainingAfter	How many moves in this same run we will have left
	 * @param maxPossibleToExplore	The most ever we could ever expose, optimal placement on board
	 * @param RunsRemaining	How many runs we have left through this same map
	 * @param percentExplored Percent of the total map that we have explored
	 * @param the max that we could possible explore currently on this map
	 * @return
	 */
	public double getEstimateOnUtility2(double probReaching, double numExposeAt, int movesAllowed, 
			double movesRemainingAfter, int maxPossibleToExplore, int RunsRemaining, double percentExplored, 
			double bestCurrPossible ){
		
		double expExpose = probReaching * numExposeAt;
		double bestPoss = ((double)maxPossibleToExplore)/((double)maxPossibleToExplore+2.0);
		double numTerminalsKnown = getNumTerminals();
		
		double fact_1;	
		if(movesRemainingAfter > 15)
			fact_1 = .5;
		else
			fact_1 = .3;
			
		// it seems that early runs, mid runs and late runs are fundamentally different in their need to explore
		if(RunsRemaining >= 7){
			if(numTerminalsKnown < this.manhattan / 5){
				// want to be more apt to explore, but this is just saying where we want to explore from
				return numExposeAt/(1+bestCurrPossible);
			}else{
				// want to be less apt to explore, but this is just saying where we want to explore from
				return numExposeAt/(1+bestCurrPossible);
			}
		}else if(RunsRemaining >= 4){
			
			
		}else{
			// we really just want to uncover as much information as possible
			return numExposeAt;
			//return expExpose/ bestPoss * fact_1 * ((double)RunsRemaining) /((double)RunsRemaining + 5.0)*(Math.exp(percentExplored)/(1+Math.exp(percentExplored)));
		}

		return expExpose/ bestPoss * fact_1 * ((double)RunsRemaining) /((double)RunsRemaining + 5.0)*(Math.exp(percentExplored)/(1+Math.exp(percentExplored)));
		
		/*(1.0*(maxPossibleToExplore/(maxPossibleToExplore+2))) *
		(movesRemainingAfter/(movesRemainingAfter+1))*
		(RunsRemaining/(RunsRemaining+5))*(Math.exp(percentExplored)/(1+Math.exp(percentExplored)));*/
	}
	
	
	
	
	
	
	
	
	
	
	
	
	
	
	
	
	
	
	
	/**
	 * Returns telling what exploration move we should do
	 * Always returns a move, so if there are only a few moves left then we should do
	 * @return
	 */
	public nextDirObject getMove(){
		this.setUpFields();
		
		int movesZeroWalkAwayDistance = MAX_COMMIT_TIME;	// walk up to 15 moves away
		int movesZero = COMMIT_TIME;
		nextDirObject myMove = new nextDirObject();
		
		if(this.numMovesRemaining == 1){
			System.out.println("killing self");
			if(numWillExpose(this.currRow, this.currColumn) > 0){
				myMove.moveDirection = MoveTypes.LOOK_AROUND;
			}else{
				// just kill yourself
				myMove.moveDirection = MoveTypes.KILL_SELF;
			}
			return myMove;
		}else if(movesZero == 1){
			if(numWillExpose(this.currRow, this.currColumn) > exploreThresh_1 * this.numPossibleExpose(this.currRow, this.currColumn) ){
				myMove.moveDirection = MoveTypes.LOOK_AROUND;
				return myMove;
			}
		}

		/* calculates the maximum possible that could ever be exposed in this map */
		int maxNum = this.getMaxPossTilesEver();
		
		// We always want to explore
		if(numRunsRemaining == 9 && numMovesUsed == 0){
			myMove.moveDirection = MoveTypes.LOOK_AROUND;
			return myMove;
		}else if(numMovesUsed == 0){
			double a_numPossExpose = maxNum;
			double a_maxPossHere = this.numPossibleExpose(this.currRow, this.currColumn);
			double a_numWillExpose = numWillExpose(this.currRow, this.currColumn);
			
			// we want to explore if worth it, if the number we could expose is big enough
			// it turns out this number is always really close to 1
			if(a_numWillExpose / (1+ a_numPossExpose) > exploreThresh_1){
				myMove.moveDirection = MoveTypes.LOOK_AROUND;
				return myMove;
			}else if( a_numWillExpose/(1+a_maxPossHere) > exploreThresh_2 || a_numWillExpose > 100){
				myMove.moveDirection = MoveTypes.LOOK_AROUND;
				return myMove;
			}else{
				System.out.println( (a_numWillExpose/(1+a_maxPossHere) > exploreThresh_2) + " vs " + exploreThresh_2 );
				// move to a location that will maximize the amount of exploring
				// we need to know the maximum amount of exploring possible at each location
				int numExposeAt[][] = new int[maxRow][maxCol];
				double[][] probReaching = new double[maxRow][maxCol];
				double[][] utilToExploreAt = new double[maxRow][maxCol];
				/* calculate a value to send into our function to maximize our utility */
				for(int r=0; r<maxRow; r++){
					for(int c=0; c<maxCol; c++){
						probReaching[r][c]= probReachSquareFromTo(this.currRow,this.currColumn, r, c, movesZeroWalkAwayDistance).probSuccess;
						
						numExposeAt[r][c] = numWillExpose(r, c);
						utilToExploreAt[r][c] = this.getEstimateOnUtility(probReaching[r][c], numExposeAt[r][c], 
								movesZeroWalkAwayDistance, Math.max(this.numMovesRemaining-movesZeroWalkAwayDistance, 0),
								maxNum, this.numRunsRemaining, this.totalNumExposed()/((double)(maxRow*maxCol)));
					}
				}
				/* now maximize our utilty */
				myMove = this.getNextDirection(utilToExploreAt, new boolean[maxRow][maxCol], walls, 
						null, null, this.currRow, this.currColumn,
						movesZeroWalkAwayDistance, 0, this.maxRow, this.maxCol,
						15, 0);
			}
		}else{
		//	System.out.println("In the else");
			/* we have used some moves see what we want to do */
			int numExposeAt[][] = new int[maxRow][maxCol];
			double[][] probReaching = new double[maxRow][maxCol];
			double[][] utilToExploreAt = new double[maxRow][maxCol];
			/* calculate a value to send into our function to maximize our utility */
			for(int r=0; r<maxRow; r++){
				for(int c=0; c<maxCol; c++){
					if(Math.abs(r-this.currRow)+ Math.abs(c-this.currColumn) > movesZeroWalkAwayDistance){
						utilToExploreAt[r][c] = 0;
						continue;
					}
					probReaching[r][c]= probReachSquareFromTo(this.currRow,this.currColumn, r, c, movesZeroWalkAwayDistance).probSuccess;
					if(probReaching[r][c] < .1){
						probReaching[r][c] = 0;
					}
					numExposeAt[r][c] = numWillExpose(r, c);
					
					utilToExploreAt[r][c] = this.getEstimateOnUtility(probReaching[r][c], numExposeAt[r][c], 
							movesZeroWalkAwayDistance, Math.max(this.numMovesRemaining-movesZeroWalkAwayDistance, 0),
							maxNum, this.numRunsRemaining, this.totalNumExposed()/((double)(maxRow*maxCol)));
					if(numWillExpose(r,c) < 40){
						utilToExploreAt[r][c] = 0;
					}
					//	System.out.println(this.currRow+","+ this.currColumn+"  to   " + r+","+c+ "   prob:: " + probReaching[r][c]+ "  with utility " + utilToExploreAt[r][c]);
				}
			}
			/* now maximize our utilty */
			myMove = this.getNextDirection(utilToExploreAt, new boolean[maxRow][maxCol], walls, 
					null, null, this.currRow, this.currColumn,
					movesZeroWalkAwayDistance, this.numMovesRemaining, this.maxRow, this.maxCol,
					10, 0);
		//	System.out.println("Attempts " + MoveTypes.toString(myMove.moveDirection));
		}
		
		/* check to see if it is worth exploring from here */
	if(true){
		double a_numPossExpose = this.numPossibleExpose((int)this.maxRow/2, (int)this.maxCol/2);
		double a_maxPossHere = this.numPossibleExpose(this.currRow, this.currColumn);
		double a_numWillExpose = numWillExpose(this.currRow, this.currColumn);
		
		System.out.println("Util:: "+ myMove.expectedUtility + (" Poss,"+ a_numPossExpose + ",will," + a_numWillExpose +",from here,"+ a_maxPossHere));
		// we want to explore if worth it, if the number we could expose is big enough
		// it turns out this number is always really close to 1
		if(a_numWillExpose / (1+ a_numPossExpose) > exploreThresh_1){
			myMove.moveDirection = MoveTypes.LOOK_AROUND;
			return myMove;
		}else if( a_numWillExpose/(1+a_maxPossHere) > exploreThresh_2 || a_numWillExpose > 120){
			myMove.moveDirection = MoveTypes.LOOK_AROUND;
			return myMove;
		}
	}	
		/* this now says to go to a spot on the board */
		if(myMove == null){
			myMove = new nextDirObject();
			myMove.moveDirection = MoveTypes.KILL_SELF;
		}else if( myMove.expectedUtility - Math.abs(Oracle.KILL_SELF_PENALTY) < 1){
			// we might want to kill ourselves
			TerminalStrategy myS = new TerminalStrategy(null);
			myS.maxCol = this.maxCol;
			myS.maxRow = this.maxRow;
			myS.setAgent(this.myAgent);
			myS.setUpFields();
			if(myS.getNearestTerminal(2,.2)){
				return myS.ifNearestTerm;
			}else{
				myMove.moveDirection = MoveTypes.KILL_SELF;
				myMove.adviseMove = true;
			}
		}
			
		return myMove;
	}

	
	
	
	
	public double getEstimateOnUtility(double probReaching, double numExposeAt, int movesAllowed, 
			double movesRemainingAfter, int maxPossibleToExplore, int RunsRemaining, double percentExplored ){
		
		double expExpose = probReaching * numExposeAt;
		double bestPoss = ((double)maxPossibleToExplore)/((double)maxPossibleToExplore+2.0);
		return (1.0*(maxPossibleToExplore/(maxPossibleToExplore+2))) *
		(movesRemainingAfter/(movesRemainingAfter+1))*
		(RunsRemaining/(RunsRemaining+5))*(Math.exp(percentExplored)/(1+Math.exp(percentExplored)));
		
		/*double fact_1;
		if(movesRemainingAfter > 15)
			fact_1 = .5;
		else
			fact_1 = .3;
		return expExpose/ bestPoss * fact_1 * ((double)RunsRemaining) /((double)RunsRemaining + 5.0)*(Math.exp(percentExplored)/(1+Math.exp(percentExplored)));
		
		return (1.0*(maxPossibleToExplore/(maxPossibleToExplore+2))) *
		(movesRemainingAfter/(movesRemainingAfter+1))*
		(RunsRemaining/(RunsRemaining+5))*(Math.exp(percentExplored)/(1+Math.exp(percentExplored)));*/
	}
}
